-
Notifications
You must be signed in to change notification settings - Fork 780
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added instrumentation for netfx SqlClient. #761
Added instrumentation for netfx SqlClient. #761
Conversation
@@ -91,11 +90,10 @@ public override void OnCustom(string name, Activity activity, object payload) | |||
|
|||
if (this.commandTypeFetcher.Fetch(command) is CommandType commandType) | |||
{ | |||
activity.AddTag(DatabaseStatementTypeSpanAttributeKey, commandType.ToString()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This change was made to avoid the .ToString
perf hit. Unrelated to the main changes, sorry.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:D I read this comment late, and was still trying to figure out this part !
Happy to see this PR! I remember doing code review when EventSource was added to SqlClient several years back. |
Testing in ApplicationInsights for SqlEventSource: We can do something similar as the minimum testing. |
src/OpenTelemetry.Instrumentation.Dependencies/Implementation/SqlEventSourceListener.netfx.cs
Outdated
Show resolved
Hide resolved
src/OpenTelemetry.Instrumentation.Dependencies/Implementation/SqlEventSourceListener.netfx.cs
Outdated
Show resolved
Hide resolved
activity.AddTag(SpanAttributeConstants.PeerServiceKey, (string)eventData.Payload[1]); | ||
activity.AddTag(SpanAttributeConstants.DatabaseInstanceKey, (string)eventData.Payload[2]); | ||
|
||
if (string.IsNullOrEmpty((string)eventData.Payload[3])) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will this throw if Payload has less than 4 count? Lets do a safety check to ensure payload[3] is fine. (some old version did not provide all the payload , maybe irrelevant now, but lets be on defensive side)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added some defensive checks. Now validates for payload being null, payload not being the expected size. Doesn't validate the types of the payload so casts could blow up. But that seemed like taking it a step too far? LMK
|
||
private static readonly Version Version = typeof(SqlEventSourceListener).Assembly.GetName().Version; | ||
private static readonly ActivitySource SqlClientActivitySource = new ActivitySource(ActivitySourceName, Version.ToString()); | ||
private static readonly EventSource SqlEventSource = (EventSource)typeof(SqlConnection).Assembly.GetType("System.Data.SqlEventSource")?.GetField("Log", BindingFlags.Static | BindingFlags.NonPublic)?.GetValue(null); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if we make a EventSource writer (i.e FakeSql : EventSource) which mimics the SqlClient event, we could unit test, right? (similar to unit tests firing DiagnosticSource events?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cijothomas I'll investigate this docker thing a bit. If it's quick to get up and running that would be great. Otherwise, I'll add some "faked" tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay.
cc: @TimothyMothra as he'd be looking at adding E2E test pipeline soon.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to get integration tests up and running on docker but ran into a problem. This is .NET Framework specific code, so we need a Windows container. That's not supported on GitHub at the moment, from what I can tell (ref). In the end, I did two things:
- Added integration tests that will skip unless an environment variable is set.
- Added fake event source tests. That gives us the same level of testing as the other Sql stuff.
I did get pretty far into getting the container up and running. What I would like to do is complete this PR, and then I can try to get Sql integration tests up and running for our .NET Core stuff? I think that will work because there are SQL Linux images and of course .NET Core SDK Linux images.
var activity = Activity.Current; | ||
if (activity == null || activity.Source != SqlClientActivitySource) | ||
{ | ||
return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we dont ever expect to this condition right? I'd suggest to add a Warning/Error log.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't this happen for samplers that return ActivityDataRequest.None
which would result in no Activity
being created? In that case Begin & End events would still fire, but Activity
would be null
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes you are right. This part is still not expected - activity.Source != SqlClientActivitySource
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my mind, it's expected. That case would be, sampler returned ActivityDataRequest.None
for our Sql activity but there is some other parent Activity
running. It could be any Activity
, really. Some parent that was sampled, and the child was None
, or it could be some random Activity
not run through a sampler at all. Something like this:
Activity myActivity = new Activity("Call SQL user activity");
myActivity.Start();
try
{
using (SqlConnection connection = new SqlConnection("Data Source=sqlserver;Initial Catalog=master;Integrated Security=True;"))
{
connection.Open();
using (SqlCommand command = new SqlCommand("select 1/1", connection))
{
command.ExecuteNonQuery();
}
}
}
catch { }
myActivity.Stop();
Log.LogActivity(myActivity);
In that case, when EndExecute
fires Activity.Current
will be non-null! The activity.Source == SqlClientActivitySource
check is to make sure it is actually our activity so we don't stop/tag something that doesn't belong to us.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perfect! thanks for clarifying.
src/OpenTelemetry.Instrumentation.Dependencies/Implementation/SqlEventSourceListener.netfx.cs
Outdated
Show resolved
Hide resolved
switch (commandType) | ||
{ | ||
case CommandType.StoredProcedure: | ||
activity.AddTag(DatabaseStatementTypeSpanAttributeKey, "StoredProcedure"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of using the magic string "StoredProcedure", i would use nameof(commandtype) or nameof(CommandType.StoredProcedure). The same comment applys for "Text" / "TableDirect"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/trace/semantic_conventions/database.md Nothing here about "Type"! should we raise an issue to add it to spec?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@eddynaka What you have against magic? j/k I added the nameof
part in both Sql instrumentation classes.
@cijothomas I'll open an issue in spec for db.statement_type
but my success rate there so far has been 0 😭 Maybe we should name it db.mssql.statement_type
? Looking at the guide, what's interesting is that a lot of the database semantics have changed since I last worked on this instrumentation. I updated them on this PR:
db.type
is nowdb.system
and we sendmssql
instead ofsql
.db.instance
is nowdb.name
.
src/OpenTelemetry.Instrumentation.Dependencies/Implementation/SqlClientDiagnosticListener.cs
Outdated
Show resolved
Hide resolved
I suggest looking into https://github.com/Deffiss/testenvironment-docker. It allow to bootstrap docker containers directly from code in unit tests setup and use it later in your tests. |
Thanks. Do you know if this unit test can be run in GH Actions? |
src/OpenTelemetry.Instrumentation.Dependencies/Implementation/SqlEventSourceListener.netfx.cs
Outdated
Show resolved
Hide resolved
src/OpenTelemetry.Exporter.Jaeger/Implementation/JaegerActivityExtensions.cs
Show resolved
Hide resolved
|
||
if (activity.IsAllDataRequested) | ||
{ | ||
activity.AddTag(SpanAttributeConstants.ComponentKey, "sql"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note to myself.
component is removed from spec and we need to remove it from this repo.
return; | ||
} | ||
|
||
string databaseName = (string)eventData.Payload[2]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wondering why setting displayname is not inside if (activity.IsAllDataRequested)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I modeled after the HttpWebRequestActivitySource:
Lines 94 to 110 in ca322b3
private static void AddRequestTagsAndInstrumentRequest(HttpWebRequest request, Activity activity) | |
{ | |
activity.DisplayName = HttpTagHelper.GetOperationNameForHttpMethod(request.Method); | |
InstrumentRequest(request, activity); | |
activity.SetCustomProperty("HttpWebRequest.Request", request); | |
if (activity.IsAllDataRequested) | |
{ | |
activity.AddTag(SpanAttributeConstants.ComponentKey, "http"); | |
activity.AddTag(SpanAttributeConstants.HttpMethodKey, request.Method); | |
activity.AddTag(SpanAttributeConstants.HttpHostKey, HttpTagHelper.GetHostTagValueFromRequestUri(request.RequestUri)); | |
activity.AddTag(SpanAttributeConstants.HttpUrlKey, request.RequestUri.OriginalString); | |
activity.AddTag(SpanAttributeConstants.HttpFlavorKey, HttpTagHelper.GetFlavorTagValueFromProtocolVersion(request.ProtocolVersion)); | |
} | |
} |
Line 96 in that snippet.
I did it there originally because the DisplayName felt useful to have always? Open to change it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got. I couldn't find any guidelines on this, so at this points, its up to individual library authors to do it. (in case adapters "we" are the authors).
|
||
string dataSource = null; | ||
|
||
using (var activityListener = new ActivityListener |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why do we need this listener here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, we don't need it. I removed from here and the tests that I copied from.
{ | ||
public class SqlEventSourceTests | ||
{ | ||
/* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Excellent! thanks for commenting the instructions here.
public const string DatabaseStatementKey = "db.statement"; | ||
public const string DatabaseStatementTypeKey = "db.statement_type"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this part of spec?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh I now recollect we already discussed this. Will need to follow up on spec. Note to myself!
@@ -40,9 +40,10 @@ public static class SpanAttributeConstants | |||
public const string HttpRouteKey = "http.route"; | |||
public const string HttpFlavorKey = "http.flavor"; | |||
|
|||
public const string DatabaseTypeKey = "db.type"; | |||
public const string DatabaseInstanceKey = "db.instance"; | |||
public const string DatabaseSystemKey = "db.system"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created #782 for this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
left couple comments.
Also there is a merge conflict. Can you take a look at that?
Codecov Report
@@ Coverage Diff @@
## master #761 +/- ##
==========================================
- Coverage 72.55% 71.01% -1.54%
==========================================
Files 223 223
Lines 7002 7007 +5
Branches 1181 1180 -1
==========================================
- Hits 5080 4976 -104
- Misses 1606 1714 +108
- Partials 316 317 +1
Continue to review full report at Codecov.
|
using var c = new HttpClient(); | ||
await c.SendAsync(request); | ||
} | ||
using var c = new HttpClient(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for cleanup!
Changes
Adds an ActivitySource for .NET Framework SqlClient BeginExecute/EndExecute events which are captured through SqlEventSource. The EventSource emits just enough information to provide similar data to what we have for .NET Core instrumentation.
Differences:
Open to suggestions on how to unit test in some useful way. I did use an actual SqlServer to make sure it worked.
Microsoft folks: What I can see as far as reference source is limited. Do you guys have access to see how far back this SqlEventSource has existed? I made the code fail gracefully if it isn't found, but it would be nice to know supported versions.
Checklist